// 
// ThreadManager.cs
//  
// Author:
//       Tomas Vondracek <tomas.vondracek@gmail.com>
// 
// Copyright (c) 2009 Tomas Vondracek
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Threading;
using ZVI.Threading.Tasks;

namespace ZVI.Threading
{

	public class GuiThreadDispatcher
	{
		/// <summary>
		/// lock object for background queue
		/// </summary>
		static object queueLockObject;
		
		/// <summary>
		/// lock object for paralel threads
		/// </summary>
		static object paralelLockObject;
		
		/// <summary>
		/// queue of background work items
		/// </summary>
		static Queue<GuiTask> works;
		
		/// <summary>
		/// list of paralel threads
		/// </summary>
		/// <remarks>for info and debug purpose so far</remarks>
		static List<Thread> paralelThreads;
		
		/// <summary>
		/// in background check the work queue
		/// </summary>
		static Thread backgroundThread;
		
		static Thread guiThread;
		
		static bool isOver = false;
		
		static ManualResetEvent resetEvent;
		
		static GuiThreadDispatcher ()
		{
			queueLockObject = new object ();
			paralelLockObject = new object ();

			resetEvent = new ManualResetEvent (false);

			works = new Queue<GuiTask> ();
			paralelThreads = new List<Thread> ();

			guiThread = Thread.CurrentThread;

			Logging.Logger.AddInfo ("GuiThreadDispatcher is starting from thread " + Thread.CurrentThread.Name);

			backgroundThread = new Thread (new ThreadStart (BackgroundWork));
			backgroundThread.IsBackground = true;
			backgroundThread.Priority = ThreadPriority.BelowNormal;
			backgroundThread.Name = "backgroundDispatcherThread";

			backgroundThread.Start ();
			
			Logging.Logger.AddThreadStart(backgroundThread);

            ThreadPool.SetMaxThreads(10, 10);
		}
		
		public static int GetCpuCount()
		{
			return Environment.ProcessorCount;
		}
		
		/// <summary>
		/// add work which has to be run from gui thread
		/// </summary>
		/// <param name="control">
		/// control where should be the work invoked from
		/// A <see cref="System.Windows.Forms.Control"/>
		/// </param>
		/// <param name="workItem">
		/// work to do
		/// A <see cref="ThreadWorkItem"/>
		/// </param>
		public static void AddBackgroundGuiWork (System.Windows.Forms.Control control, GuiTask workItem)
		{
			workItem.IsGuiWork = true;
			workItem.Control = control;

			lock (queueLockObject)
			{
				works.Enqueue (workItem);
				//Console.WriteLine ("Adding background GUI work " + workItem.Description);

                Logging.Logger.AddInfo("Adding background GUI work " + workItem.Description);
				resetEvent.Set();
			}
		}
		
		/// <summary>
		/// add non-gui related work to do in background
		/// </summary>
		/// <param name="workItem">
		/// work to do
		/// A <see cref="ThreadWorkItem"/>
		/// </param>
		public static void AddBackgroundWorkAsync (GuiTask workItem)
		{
			lock (queueLockObject)
			{
				works.Enqueue (workItem);
				Console.WriteLine ("Adding background work " + workItem.Description);
				
				resetEvent.Set ();
				//Logging.Logger.AddInfo ("Adding background GUI work " + workItem.Description);
			}
		}

        //public static void AddWorkToThreadPool(ThreadWorkItem workItem)
        //{
        //    ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(workItem.Work));
        //}
		
		/// <summary>
		/// create new thread and run it immediatly
		/// </summary>
		/// <param name="paralelWorkItem">
		/// A <see cref="ThreadWorkItem"/>
		/// </param>
		public static Thread AddParalelWork (AsyncTask paralelWorkItem)
		{
			Thread t = null;
			lock (paralelLockObject)
			{
				//paralelWorkItem.OnFinished += HandleParalelWorkItemOnFinished;;
				t = new Thread (new ThreadStart (paralelWorkItem.Run));	
				t.Priority = ThreadPriority.Normal;
				t.IsBackground = true;
				t.Name = "ParalelWorkThread" + paralelWorkItem.Description;

				paralelThreads.Add (t);

				t.Start ();
				
				Logging.Logger.AddThreadStart (t);
			}
			return t;
		}

		static void HandleParalelWorkItemOnFinished (Thread t, AsyncTask task)
		{
			
		}
		
		/// <summary>
		/// create new thread and run it immediatly
		/// </summary>
		/// <param name="start">
		/// A <see cref="ThreadStart"/>
		/// </param>
		public static Thread AddParalelWork (ThreadStart start)
		{
			Thread t = null;
			lock (paralelLockObject)
			{
				t = new Thread (start);	
				t.Priority = ThreadPriority.Normal;
				t.IsBackground = true;
				t.Name = "ParalelWork" + start.Method.Name;

				paralelThreads.Add (t);

				t.Start ();
				
				Logging.Logger.AddThreadStart (t);
			}
			return t;
		}
		
		/// <summary>
		/// test if current thread is gui thread
		/// </summary>
		public static bool IsGuiThread
		{
			get {
				return guiThread == Thread.CurrentThread;
			}
		}
		
		/// <summary>
		/// will destroy any work in background queue
		/// </summary>
		public static void FreeEverething()
		{
			lock (queueLockObject)
			{
				works.Clear();
			}
		}
		
		private static void BackgroundWork ()
		{
			while (!isOver)
			{
				while(works.Count > 0)
				{
					GuiTask work = works.Dequeue ();

					if (work.IsGuiWork)
					{
						work.Control.Invoke (work.Work);
					}
					else
					{
						work.Run ();
					}
				}
				
				//pause background thread running until there's some work to do
				resetEvent.Reset ();
				resetEvent.WaitOne();
			}
		}
		
		public static void Exit ()
		{
			lock (queueLockObject)
			{
				isOver = true;
			}

			lock (paralelLockObject)
			{
				Logging.Logger.AddInfo ("Paralel threads, count = " + paralelThreads.Count);

				foreach (Thread thread in paralelThreads)
				{
					if (thread.IsAlive)
					{
						//thread.Join ();
						Logging.Logger.AddThreadStart (thread);
					}
				}
			}
			
			Logging.Logger.AddInfo("GuiDispatcher is quiting...");
		}
	}
}
